#!/usr/bin/python
# ljuntagged v1.1
# Export your untagged LJ(-esque) entries to an .htm file.
#
# Originally by Greg Hewgill:
# http://hewgill.com/ljdump/
#
# Modified by Xtina Schelin:
# http://xtina.dreamwidth.org/
#
# LICENSE:
# This software is provided 'as-is', without any express or implied
# warranty.  In no event will the author be held liable for any damages
# arising from the use of this software.
#
# Permission is granted to anyone to use this software for any purpose,
# including commercial applications, and to alter it and redistribute it
# freely, subject to the following restrictions:
#
# 1. The origin of this software must not be misrepresented; you must not
#        claim that you wrote the original software. If you use this software
#        in a product, an acknowledgment in the product documentation would be
#        appreciated but is not required.
# 2. Altered source versions must be plainly marked as such, and must not be
#        misrepresented as being the original software.
# 3. This notice may not be removed or altered from any source distribution.

import codecs, hashlib, os, pickle, pprint, re, shutil, sys, urllib2, xml.dom.minidom, xmlrpclib
from xml.sax import saxutils

def calcchallenge(challenge, password):
        return hashlib.md5(challenge + hashlib.md5(password).hexdigest()).hexdigest()

def flatresponse(response):
        r = {}
        while True:
                name = response.readline()
                if len(name) == 0:
                        break
                if name[-1] == '\n':
                        name = name[:len(name)-1]
                value = response.readline()
                if value[-1] == '\n':
                        value = value[:len(value)-1]
                r[name] = value
        return r

def getljsession(server, username, password):
        r = urllib2.urlopen(server+"/interface/flat", "mode=getchallenge")
        response = flatresponse(r)
        r.close()
        r = urllib2.urlopen(server+"/interface/flat", "mode=sessiongenerate&user=%s&auth_method=challenge&auth_challenge=%s&auth_response=%s" % (username, response['challenge'], calcchallenge(response['challenge'], password)))
        response = flatresponse(r)
        r.close()
        return response['ljsession']

def dochallenge(server, params, password):
        challenge = server.LJ.XMLRPC.getchallenge()
        params.update({
                'auth_method': "challenge",
                'auth_challenge': challenge['challenge'],
                'auth_response': calcchallenge(challenge['challenge'], password)
        })
        return params

# Write the item.
def dumpelement(f, e):
        f.write("<li>")
        theSubj = "[no subject]"
        useSec = ""
        # Only grab the entry elements that matter.
        for k in e.keys():
                if e[k] == "private":
                        useSec = "private"
                if k == "allowmask":
                        if e[k] == 1:
                                useSec = "friends"
                        else:
                                useSec = "custom"
                if k == "eventtime":
                        theDate = e[k]
                if k == "url":
                        theUrl = e[k]
                if k == "subject":
                        theSubj = e[k]

        # Security setting icon!  Yay!
        if useSec == "private":
                theSec = "<img src=\"http://l-stat.livejournal.com/img/icon_private.gif\" />"
        elif useSec == "friends":
                theSec = "<img src=\"http://l-stat.livejournal.com/img/icon_protected.gif\" />"
        elif useSec == "custom":
                theSec = "<img src=\"http://l-stat.livejournal.com/img/icon_groups.gif\" />"
        else:
                theSec = ""

        # If there's a unicode issue.
        try:
                theSubj = unicode(str(theSubj), "UTF-8")
        except UnicodeDecodeError:
                theSubj = unicode(str(theSubj), "cp1252")

        # Export bullet point to file.
        f.write("[%s]:  %s <a href=\"%s\" target=\"_blank\">%s</a></li>\n" % (theDate, theSec, theUrl, theSubj))

def writedump(filename, event):
        # Only continue if 'taglist' is in the entry's elements list.
        # Yes, this is ugly.  :/
        isTagged = "false"
        for k in event.keys():
                if k == "props":
                        for q in event[k].keys():
                                if q == "taglist":
                                        isTagged = "true"
        if isTagged == "false":
                f = codecs.open(filename, "a", "UTF-8")
                dumpelement(f, event)
                f.close()

# The main function.
def ljlist(Server, Username, Password, Journal):
        m = re.search("(.*)/interface/xmlrpc", Server)
        if m:
                Server = m.group(1)
        if Username != Journal:
                authas = "&authas=%s" % Journal
        else:
                authas = ""

        print "Logging into '%s'..." % Journal

        ljsession = getljsession(Server, Username, Password)
        server = xmlrpclib.ServerProxy(Server+"/interface/xmlrpc")
        newentries = 0
        errors = 0
        lastsync = ""

        try:
                r = server.LJ.XMLRPC.login(dochallenge(server, {
                        'username': Username,
                        'ver': 1
                }, Password))
        except:
                print
                print "Couldn't log in.  Please try again."
                sys.exit()

        print "Success!"
	print

        try:
                f = codecs.open("%s.htm" % Journal, "w", "UTF-8")
                f.write("<html><body><ol>\n");
                f.close()
        except:
                pass


        while True:
                r = server.LJ.XMLRPC.syncitems(dochallenge(server, {
                        'username': Username,
                        'ver': 1,
                        'lastsync': lastsync,  # This needs to be present, else it loops forever.
                        'usejournal': Journal,
                }, Password))
                if len(r['syncitems']) == 0:
                        break
                for item in r['syncitems']:
                        # If the item is a proper enty.
                        if item['item'][0] == 'L':
                                # So that the user knows where it's at, and that it hasn't died.
                                print "Checking journal entry %s for tags." % item['item']
                                try:
                                        e = server.LJ.XMLRPC.getevents(dochallenge(server, {
                                                'username': Username,
                                                'ver': 1,
                                                'selecttype': "one",
                                                'itemid': item['item'][2:],
                                                'usejournal': Journal,
                                        }, Password))
                                        if e['events']:
                                                writedump( "%s.htm" % Journal, e['events'][0])
                                                newentries += 1
                                        else:
                                                print "Unexpected empty item: %s" % item['item']
                                                errors += 1
                                except xmlrpclib.Fault, x:
                                        print "Error getting item: %s" % item['item']
                                        pprint.pprint(x)
                                        errors += 1
                        lastsync = item['time']

        # At the end of things, close the file out.
        f = open("%s.htm" % Journal, "a")
        f.write("</ol></body></html>")
        f.close()


        # Closing message.
        print
        print "Success!  %d entries checked." % (newentries)
        if errors > 0:
                print "%d errors" % errors

# Primary interface.
if __name__ == "__main__":
        print "ljuntagged: Export your untagged LJ-esque entries to an .htm file."
        print

        # If the .config file has been found...
        if os.access("ljuntagged.config", os.F_OK):
                config = xml.dom.minidom.parse("ljuntagged.config")
                server = config.documentElement.getElementsByTagName("server")[0].childNodes[0].data
		# Confirm that the URL is at least marginally correct.
                try:
                        urllib2.urlopen(server)
                except:
                        print
                        print "The server URL supplied in the .config file is incorrect."
                        sys.exit()

                username = config.documentElement.getElementsByTagName("username")[0].childNodes[0].data
                password = config.documentElement.getElementsByTagName("password")[0].childNodes[0].data
                journals = config.documentElement.getElementsByTagName("journal")
                if journals:
                        for e in journals:
                                ljlist(server, username, password, e.childNodes[0].data)
                else:
                        ljlist(server, username, password, username)

        # ...otherwise.
        else:
                # Ask for the settings.
                from getpass import getpass
                print "Enter % at any prompt to exit this program."
                print
                server = raw_input("Server [LJ]: ")
                if server == '%':
                        print "Okay, bye!"
                        sys.exit()

                # Format server input.
                if len(server) == 0:
                        server = "http://www.livejournal.com"
                elif server.lower() == "lj":
                        server = "http://www.livejournal.com"
                elif server.lower() == "dw":
                        server = "http://www.dreamwidth.org"
                elif server[:7] != "http://":
                        server = "http://%s" % server

                # Confirm that the URL is at least marginally correct.
                try:
                        urllib2.urlopen(server)
                except:
                        print
                        print "That is an invalid URL."
                        sys.exit()
               
                # Username.
                username = raw_input("Username: ")
                if server == '%':
                        print "Okay, bye!"
                        sys.exit()

                # Password.
                password = getpass("Password: ")
                if server == '%':
                        print "Okay, bye!"
                        sys.exit()

                print
                print "You may export untagged entries for either your own journal or a community."
                print
                journal = raw_input("Export untagged entries for [%s]: " % username)
                if server == '%':
                        print "Okay, bye!"
                        sys.exit()

                print
                if journal:
                        ljlist(server, username, password, journal)
                else:
                        ljlist(server, username, password, username)
